package com.ipan.kits.io;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.common.io.Closeables;
import com.ipan.kits.io.exception.IORuntimeException;
import com.ipan.kits.text.Charsets;
import com.ipan.kits.text.Convert;

/**
 * IO Stream/Reader相关工具集. 固定encoding为UTF8.
 * 
 * 建议使用Apache Commons IO和Guava关于IO的工具类(com.google.common.io.*), 在未引入Commons IO时可以用本类做最基本的事情.
 * 
 * 1. 安静关闭Closeable对象
 * 
 * 2. 读出InputStream/Reader全部内容到String 或 List<String>
 * 
 * 3. 读出InputStream/Reader一行内容到String
 * 
 * 4. 将String写到OutputStream/Writer
 * 
 * 5. 将String 转换为InputStream/Reader
 * 
 * 5. InputStream/Reader与OutputStream/Writer之间复制的copy
 * 
 */
public class IOUtil {

	private static Logger logger = LoggerFactory.getLogger(IOUtil.class);

	/**
	 * 在final中安静的关闭, 不再往外抛出异常避免影响原有异常，最常用函数. 同时兼容Closeable为空未实际创建的情况.
	 * 
	 * @see {@link Closeables#close}
	 */
	public static void closeQuietly(Closeable closeable) {
		if (closeable == null) {
			return;
		}
		try {
			closeable.close();
		} catch (IOException e) {
			logger.warn("IOException thrown while closing Closeable.", e);
		}
	}

	/**
	 * 简单读取InputStream到String.
	 */
	public static String toString(InputStream input) throws IOException {
		InputStreamReader reader = new InputStreamReader(input, Charsets.UTF_8);
		return toString(reader);
	}
	public static String toString(InputStream input, Charset charset) throws IOException {
		InputStreamReader reader = new InputStreamReader(input, charset);
		return toString(reader);
	}

	/**
	 * 简单读取Reader到String
	 * 
	 * @see {@link CharStreams#toString}
	 */
	public static String toString(Reader input) throws IOException {
		return CharStreams.toString(input);
	}

	/**
	 * 简单读取Reader的每行内容到List<String>
	 */
	public static List<String> toLines(final InputStream input) throws IOException {
		return CharStreams.readLines(new BufferedReader(new InputStreamReader(input, Charsets.UTF_8)));
	}

	/**
	 * 简单读取Reader的每行内容到List<String>
	 * 
	 * @see {@link CharStreams#readLines}
	 */
	public static List<String> toLines(final Reader input) throws IOException {
		return CharStreams.readLines(toBufferedReader(input));
	}

	/**
	 * 读取一行数据，比如System.in的用户输入
	 */
	public static String readLine(final InputStream input) throws IOException {
		return new BufferedReader(new InputStreamReader(input, Charsets.UTF_8)).readLine();
	}

	/**
	 * 读取一行数据
	 */
	public static String readLine(final Reader reader) throws IOException {
		return toBufferedReader(reader).readLine();
	}

	/**
	 * 简单写入String到OutputStream.
	 */
	public static void write(final String data, final OutputStream output) throws IOException {
		if (data != null) {
			output.write(data.getBytes(Charsets.UTF_8));
		}
	}

	/**
	 * 简单写入String到Writer.
	 */
	public static void write(final String data, final Writer output) throws IOException {
		if (data != null) {
			output.write(data);
		}
	}
	
	/**
	 * 将多部分内容写到流中，自动转换为字符串
	 *
	 * @param out        输出流
	 * @param charset    写出的内容的字符集
	 * @param isCloseOut 写入完毕是否关闭输出流
	 * @param contents   写入的内容，调用toString()方法，不包括不会自动换行
	 * @throws IORuntimeException IO异常
	 * @since 3.0.9
	 */
	public static void write(OutputStream out, Charset charset, boolean isCloseOut, String... contents) throws IOException {
		OutputStreamWriter osw = null;
		try {
			osw = getWriter(out, charset);
			for (Object content : contents) {
				if (content != null) {
					osw.write(Convert.toStr(content, StringUtils.EMPTY));
				}
			}
			osw.flush();
		} catch (IOException e) {
			throw e;
		} finally {
			if (isCloseOut) {
				closeQuietly(osw);
			}
		}
	}

	/**
	 * 字符串转换成InputStream
	 */
	public static InputStream toInputStream(String input) {
		return new ByteArrayInputStream(input.getBytes(Charsets.UTF_8));
	}

	/**
	 * 字符串转换成Reader
	 */
	public static Reader toInputStreamReader(String input) {
		return new InputStreamReader(toInputStream(input), Charsets.UTF_8);
	}

	/**
	 * 在Reader与Writer间复制内容
	 * 
	 * @see {@link CharStreams#copy}
	 */
	public static long copy(final Reader input, final Writer output) throws IOException {
		return CharStreams.copy(input, output);
	}

	/**
	 * 在InputStream与OutputStream间复制内容
	 * 
	 * @see {@link ByteStreams#copy}
	 */
	public static long copy(final InputStream input, final OutputStream output) throws IOException {
		return ByteStreams.copy(input, output);
	}

	public static BufferedReader toBufferedReader(final Reader reader) {
		return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
	}
	
	/**
     * 打开文件的输入流，提供了比<code>new FileInputStream(file)</code>更好更优雅的方式.
     *
     * @param file 文件
     * @return {@link FileInputStream}
     * @throws FatalFileException 文件处理异常
     */
    public static FileInputStream openFileInputStream(File file) {
        Validate.notNull(file, "File must not be null.");
        if (file.exists()) {
            if (file.isDirectory()) {
                throw new RuntimeException("File '" + file + "' exists but is a directory");
            }
            if (!file.canRead()) {
                throw new RuntimeException("File '" + file + "' cannot be read");
            }
            try {
                return new FileInputStream(file);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        throw new RuntimeException("File '" + file + "' does not exist");
    }

    /**
     * 打开件输出流
     *
     * @param file 文件
     * @return {@link FileOutputStream}
     */
    public static FileOutputStream openFileOutputStream(File file) {
        return openFileOutputStream(file, false);
    }

    /**
     * 打开件输出流
     *
     * @param file   文件
     * @param append 附加
     * @return {@link FileOutputStream}
     */
    private static FileOutputStream openFileOutputStream(File file, boolean append) {
    	Validate.notNull(file, "File must not be null.");
        if (file.exists()) {
            if (file.isDirectory())
                throw new RuntimeException("Destination [" + file + "] exists but is a directory.");
            if (!file.canWrite())
                throw new RuntimeException(String.format("Destination [%s] exists but cannot write.", file));
        } else {
            File parent = file.getParentFile();
            if (parent != null) {
                if (!parent.mkdirs() && !parent.isDirectory())
                    throw new RuntimeException("Directory [" + parent + "] could not be created.");
            }
        }
        try {
            return new FileOutputStream(file, append);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
	 * 获得一个Writer
	 *
	 * @param out     输入流
	 * @param charset 字符集
	 * @return OutputStreamWriter对象
	 */
	public static OutputStreamWriter getWriter(OutputStream out, Charset charset) {
		if (null == out) {
			return null;
		}

		if (null == charset) {
			return new OutputStreamWriter(out);
		} else {
			return new OutputStreamWriter(out, charset);
		}
	}
	
}
